/**
 * @file
 * @brief KL15检测任务
 * @author
 * + 隐星魂 (Roy Sun) <xwos@xwos.tech>
 * @copyright
 * + Copyright © 2015 xwos.tech, All Rights Reserved.
 * > Licensed under the Apache License, Version 2.0 (the "License");
 * > you may not use this file except in compliance with the License.
 * > You may obtain a copy of the License at
 * >
 * >         http://www.apache.org/licenses/LICENSE-2.0
 * >
 * > Unless required by applicable law or agreed to in writing, software
 * > distributed under the License is distributed on an "AS IS" BASIS,
 * > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * > See the License for the specific language governing permissions and
 * > limitations under the License.
 */

#include "board/std.h"
#include <xwos/osal/thd.h>
#include <xwos/osal/pm.h>
#include <xwos/osal/sync/sem.h>
#include <xwcd/ds/soc/gpio.h>
#include "RTD/xwds/device.h"
#include "Icu.h"
#include "App/kl15.h"

#define KL15_ICU_CH 0U
#define KL15_GPIO_PORT XWDS_GPIO_PORT_A
#define KL15_GPIO_PIN XWDS_GPIO_PIN_30

#define KL15_THD_PRIORITY XWOS_SKD_PRIORITY_DROP(XWOS_SKD_PRIORITY_RT_MAX, 0)
#define KL15_TASK_PERIOD (xwtm_ms(100))
#define KL15_STANDBY_TIMEOUT 60U

enum power_mode_em {
        PWRMODE_STANDBY,
        PWRMODE_RUNNING,
};

struct kl15_data {
        bool state;
        enum power_mode_em power_mode;
        xwsq_t standby_timeout_count;
        struct xwos_sem sem;
        struct xwos_thd thd;
        xwos_thd_d thdd;
};

struct kl15_data kl15 = {
        .state = false,
        .power_mode = PWRMODE_RUNNING,
        .standby_timeout_count = 0,
};

__xwcc_alignl1cache xwu8_t kl15_thd_stack[2048] = {0};


static void kl15_switch_pm_running2standby(void)
{
        kl15.power_mode = PWRMODE_STANDBY;
}

static void kl15_pm_fsm_running(void)
{
        if (kl15.state) {
                kl15.standby_timeout_count = 0;
        } else {
                kl15.standby_timeout_count++;
                if (kl15.standby_timeout_count > KL15_STANDBY_TIMEOUT) {
                        kl15_switch_pm_running2standby();
                }
        }
}

static void kl15_switch_pm_standby2running(void)
{
        kl15.power_mode = PWRMODE_RUNNING;
}

static void kl15_pm_fsm_standby(void)
{
        if (kl15.state) {
                kl15.standby_timeout_count = 0;
                kl15_switch_pm_standby2running();
        } else {
                xwos_pm_suspend();
        }
}

static void kl15_pm_fsm(void)
{
        switch (kl15.power_mode) {
        case PWRMODE_RUNNING:
                kl15_pm_fsm_running();
                break;
        case PWRMODE_STANDBY:
        default:
                kl15_pm_fsm_standby();
                break;
        }
}

static xwer_t kl15_detect(void)
{
        xwer_t rc;
        xwsq_t in;

        rc = xwos_sem_wait_to(&kl15.sem, xwtm_ft(KL15_TASK_PERIOD));
        if ((XWOK == rc) || (-ETIMEDOUT == rc)) {
                xwds_gpio_input(&mcalxwds_soc, KL15_GPIO_PORT, KL15_GPIO_PIN, &in);
                if (in) {
                        kl15.state = true;
                } else {
                        kl15.state = false;
                }
        }
        return rc;
}

static void kl15_suspend(void)
{
}

static void kl15_resume(void)
{
}

static xwer_t kl15_task(void * arg)
{
        xwer_t rc;

        xwds_gpio_req(&mcalxwds_soc, KL15_GPIO_PORT, KL15_GPIO_PIN);
        XWOS_UNUSED(arg);
        while (!xwos_cthd_shld_stop()) {
                if (xwos_cthd_shld_frz()) {
                        kl15_suspend();
                        xwos_cthd_freeze();
                        kl15_resume();
                } else {
                        rc = kl15_detect();
                        if ((XWOK == rc) || (-ETIMEDOUT == rc)) {
                                kl15_pm_fsm();
                        } else if (-EINTR == rc) {
                        } else {
                                break;
                        }
                }
        }
        return XWOK;
}

void kl15_notification(void)
{
        if (XWOS_PM_STAGE_SUSPENDED == xwos_pm_get_stage()) {
                xwos_pm_resume();
        }
        xwos_sem_post(&kl15.sem);
}

void kl15_start_detection(void)
{
        xwer_t rc;
        struct xwos_thd_attr attr;

        xwos_sem_init(&kl15.sem, 0, 1);
        xwos_thd_attr_init(&attr);
        attr.name = "kl15.thd";
        attr.stack = (xwstk_t *)kl15_thd_stack;
        attr.stack_size = sizeof(kl15_thd_stack);
        attr.priority = KL15_THD_PRIORITY;
        attr.detached = true;
        attr.privileged = true;
        rc = xwos_thd_init(&kl15.thd, &kl15.thdd,
                           &attr,
                           kl15_task,
                           NULL);
        if (XWOK == rc) {
                Icu_Init(&Icu_Config_BOARD_InitPeripherals);
                Icu_EnableWakeup(KL15_ICU_CH);
                Icu_EnableNotification(KL15_ICU_CH);
                Icu_EnableEdgeDetection(KL15_ICU_CH);
        }
}

void kl15_stop_detection(void)
{
        xwer_t rc;
        xwer_t trc;

        Icu_DisableEdgeDetection(KL15_ICU_CH);
        Icu_DisableNotification(KL15_ICU_CH);
        Icu_DisableWakeup(KL15_ICU_CH);
        Icu_DeInit();
        rc = xwos_thd_stop(kl15.thdd, &trc);
        if (XWOK == rc) {
                xwos_sem_fini(&kl15.sem);
        }
}
